/*
 * Copyright (c) 2000-2015 Tacke Consulting, Inc
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions 
 * of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 * 
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Diagnostics;

namespace OpenNETCF.Media
{
    /// <summary>
    /// Base media player abstract class.
    /// </summary>
    public abstract class BaseMediaPlayer : Control
    {
        /// <summary>
        /// Occurs when the state of the media file has changed, such as from play to stop or from play to pause
        /// </summary>
        public event MediaStateChangedEventHandler MediaStateChanged;

        /// <summary>
        /// Occurs a new media file has been opened.
        /// </summary>
        public event MediaOpenedEventHandler MediaOpened;

        /// <summary>
        /// Occurs the position of the media has changed
        /// </summary>
        public event MediaPositionChangedEventHandler MediaPositionChanged;

        /// <summary>
        /// Occurs when there is an exception during playback of the media
        /// </summary>
        public event MediaPlaybackErrorEventHandler MediaPlaybackError;

        /// <summary>
        /// The background image to display in the control
        /// </summary>
        internal Bitmap m_backgroundImage;

        /// <summary>
        /// The current media file to play with the media player
        /// </summary>
        internal string m_mediaFile;

        /// <summary>
        /// Value to indicate if player should be in fullscreen mode or not
        /// </summary>
        internal bool m_fullscreen = false;

        internal bool m_autoPlay = false;

        internal double m_startTime = 0;
        internal double m_segmentLength = 0;
        private MediaStateChangedEventHandler m_startPositionSetter;
        
        /// <summary>
        /// Default constructor
        /// </summary>
        public BaseMediaPlayer()
        {          
            if (!IsDesignTime)
            {
                //Initialize control
                this.Initialize();
            }
        }

        /// <summary>
        /// Closes and releases the current media resource
        /// </summary>
        public abstract void Close();

        /// <summary>
        /// Initializes the media player control
        /// </summary>
        internal abstract void Initialize();

        /// <summary>
        /// Gets or sets a value indicating if media will begin playback automatically when the Source property is set
        /// </summary>
        public abstract bool AutoPlay { get;set;}

        /// <summary>
        /// Gets or sets the location within the media file (in seconds) to start playing from.
        /// </summary>
        public abstract double StartTime { get; set;}

        /// <summary>
        /// Gets or sets the length of media file segment (in seconds) that will be played (default is to the end).
        /// </summary>
        public abstract double SegmentLength { get; set; }

        /// <summary>
        /// Gets the duration of the media
        /// <remarks>The duration cannot be determined until after the MediaOpened event has occured.  MediaOpened event is triggered by the MediaUrl property.</remarks>
        /// </summary>
        public abstract double Duration { get; }

      /// <summary>
        /// Gets the current position of the Media file.
        /// </summary>
        public abstract double Position { get; set; }

        /// <summary>
        /// Gets or sets a value to set the media player to full screen
        /// </summary>
        public abstract bool FullScreen{get; set;}

        /// <summary>
        /// Gets the current state of the media associated with the media player
        /// </summary>
        public abstract MediaState MediaState { get;}

        /// <summary>
        /// Gets or sets the URL to a media source. The media source can be of any type supported by the media player.
        /// </summary>
        public abstract string MediaUrl { get; set;}

        /// <summary>
        /// Plays the media file within the media player control
        /// </summary>
        internal abstract void PlayInternal();

        /// <summary>
        /// Stops the media file within the media play control
        /// </summary>
        internal abstract void StopInternal();

        /// <summary>
        /// Pauses the media file within the media player control
        /// </summary>
        internal abstract void PauseInternal();

        /// <summary>
        /// Sets the bounds of the media player control
        /// </summary>
        /// <param name="bounds"></param>
        internal abstract void SetMediaPlayerBoundsInternal(Rectangle bounds);

        /// <summary>
        /// Starts playing the media within the control
        /// </summary>
        public void Play()
        {
            PlaySegment(0, 0);
        }

        /// <summary>
        /// Plays media starting with specified position until given amount of media has been played
        /// </summary>
        /// <param name="start">Where to start (seconds)</param>
        /// <param name="length">How much to play (seconds)</param>
        public void PlaySegment(double start, double length)
        {
            CheckEvaluation();

            if (!IsDesignTime)
            {
                if (MediaUrl == null || MediaUrl.Length == 0)
                    throw new MediaPlayerException("You must set the media file first before attempting to play media.");

                // We cannot verify these values until the media is actually open
                m_startTime = start;
                m_segmentLength = length;

                if (MediaState == MediaState.Paused ||
                    MediaState == MediaState.Stopped)
                {
                    PlayInternal();
                    //Raise the event
                    OnMediaStateChanged(new MediaStateChangedEventArgs(MediaState.Playing));
                }
            }
        }

        // One-shot event handler to set the start position
        private void SetStartPosition(MediaStateChangedEventArgs args)
        {
            if (args.MediaState == MediaState.Playing)
            {
                // Remove ourselves from the event handler chain
                this.MediaStateChanged -= m_startPositionSetter;
                m_startPositionSetter = null;
                // Set start position
                Position = m_startTime;
            }
        }

        private int m_playCount;
        private bool m_evalDialog = false;
        private void CheckEvaluation()
        {
#if EVAL
            if (m_playCount++ > 5)
            {
                m_evalDialog = true;
                MessageBox.Show("This is an evaluation version of the OpenNETCF Media Player Control.  Visit www.OpenNETCF.com for licensing information");
                m_evalDialog = false;
            }
#endif

        }

        /// <summary>
        /// Overloaded. Starts playing the media within the control
        /// </summary>
        public void Play(string mediaFile)
        {
            MediaUrl = mediaFile;
            Play();
        }

        /// <summary>
        /// Pauses the current media file
        /// </summary>
        public void Pause()
        {
            if (!IsDesignTime)
            {
                if (MediaState == MediaState.Playing)
                {
                    PauseInternal();
                    //Raise the event
                    OnMediaStateChanged(new MediaStateChangedEventArgs(MediaState.Paused));
                }
            }
        }

        /// <summary>
        /// Stops playing the media currently playing within the control
        /// </summary>
        public void Stop()
        {
            if (MediaState == MediaState.Playing)
            {
                StopInternal();
                //Raise the event
                OnMediaStateChanged(new MediaStateChangedEventArgs(MediaState.Stopped));
            }
        }

        /// <summary>
        /// Raises the OnPaint event
        /// </summary>
        /// <param name="pe"></param>
        protected override void OnPaint(PaintEventArgs pe)
        {
            if (m_backgroundImage != null)
                pe.Graphics.DrawImage(m_backgroundImage, 0, 0);
        }

        internal abstract Bitmap DesignerBackgroundImage { get;}

        internal abstract DebugLogger Logger { get;}

        /// <summary>
        /// Raises the OnResize event
        /// </summary>
        /// <param name="e"></param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            if (m_backgroundImage != null)
                m_backgroundImage.Dispose();

            if (IsDesignTime)
            {
                m_backgroundImage = new Bitmap(this.Width, this.Height);
                using (Graphics g = Graphics.FromImage(m_backgroundImage))
                {
                    ImageAttributes ia = new ImageAttributes();
                    Bitmap temp = DesignerBackgroundImage;
                    ia.SetColorKey(temp.GetPixel(0, 0), temp.GetPixel(0, 0));
                    Rectangle dest = new Rectangle(0, 0, this.Width, this.Height);
                    Rectangle src = new Rectangle(0, 0, temp.Width, temp.Height);
                    g.DrawImage(temp, dest, src.X, src.Y, src.Width, src.Height, GraphicsUnit.Pixel, ia);
                    using (Pen p = new Pen(Color.Black, 2))
                    {
                        g.DrawRectangle(p, 1, 1, this.Width - 2, this.Height - 2);
                    }
                }
            }
            else
            {
                this.SetMediaPlayerBoundsInternal(new Rectangle(0, 0, this.Width, this.Height));
            }
        }

        internal void OnMediaStateChanged(MediaStateChangedEventArgs e)
        {
            if (MediaStateChanged != null)
                MediaStateChanged(e);
        }

        internal virtual void OnMediaOpened(double duration)
        {
            if (InvokeRequired)
            {
                Invoke(new MediaOpenedEventHandler(OnMediaOpened), duration);
                return;
            }

            Logger.WriteLine(string.Format("+Base.OnMediaOpened({0})", duration));
            if (MediaOpened != null)
            {
                Logger.WriteLine(" Raising MediaOpened event");
                try
                {
                    MediaOpened(duration);
                }
                catch 
                {
                    // ignore exception inside the application
                }
            }

            if (m_startTime > this.Duration)
                throw new MediaPlayerException("Start point is past the end of the file.");

            if (m_segmentLength > this.Duration - m_startTime)
                m_segmentLength = this.Duration - m_startTime;

            if (m_startTime > 0)
            {
                this.MediaStateChanged += m_startPositionSetter = new MediaStateChangedEventHandler(SetStartPosition);
            }

            Logger.WriteLine("-Base.OnMediaOpened");
        }

        internal virtual void OnMediaPositionChanged(double duration, double position)
        {
            if (MediaState != MediaState.Playing)
                return;

            if (MediaPositionChanged != null)
            {
                MediaPositionChanged(new MediaPositionChangedEventArgs(duration, position));
            }

            if (m_segmentLength != 0 && position > m_startTime + m_segmentLength)
                Stop();
        }

        internal void OnMediaPlaybackError(MediaStateChangedEventArgs state, ExceptionEventArgs exceptionEventArgs)
        {
            if (MediaPlaybackError != null)
            {
                MediaPlaybackError(state, exceptionEventArgs);
            }
        }

        private bool m_isDesignTimeChecked;
        private bool m_isDesignTime;
        internal bool IsDesignTime
        {
            get
            {
                if (!m_isDesignTimeChecked)
                {
                    try
                    {
                        // Determine if this instance is running against .NET Framework by using the MSCoreLib PublicKeyToken
                        System.Reflection.Assembly mscorlibAssembly = typeof(int).Assembly;
                        if ((mscorlibAssembly != null))
                        {
                            if (mscorlibAssembly.FullName.ToUpper().EndsWith("B77A5C561934E089"))
                            {
                                m_isDesignTime = true;
                            }
                            else
                            {
                                m_isDesignTime = false;
                            }
                        }
                    }
                    finally
                    {
                        m_isDesignTimeChecked = true;
                    }
                }

                return m_isDesignTime;
            }
        }

    }
}
